﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Fuse8.DomainFramework.DataAccess.ExpressionSlicers;
using Fuse8.DomainFramework.Domain;
using Fuse8.DomainFramework.DataAccess;
using Fuse8.DomainFramework.Common;
using Fuse8.DomainFramework.Exceptions;
using Fuse8.DomainFramework.CacheService;
using Fuse8.DomainFramework.ValidationService;
using Fuse8.DomainFramework.Common.Exceptions;
using Fuse8.DomainFramework.Common.Locator;
using Fuse8.DomainFramework.Common.Pipeline;
using Fuse8.DomainFramework.Configuration;
using Fuse8.DomainFramework.Common.Logging;

namespace Fuse8.DomainFramework.BusinessService
{
    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    /// <typeparam name="TKey"></typeparam>
    public abstract class BaseBusinessService<TEntity, TKey> : IEntityBusinessService<TEntity, TKey>
        where TEntity : class, IEntityObject<TKey>, new()
        where TKey : IComparable<TKey>, IEquatable<TKey> 
    {
        #region Fields

        private bool _canCaching;
        private bool _isNeedToCheckForCaching = true;
        private readonly string _typeName = typeof(TEntity).Name;
        private readonly RuntimeTypeHandle _typeHandle = typeof(TEntity).TypeHandle;

        private IEntityDataRepository<TEntity, TKey> _entityDataRepository;
        private IEntityValidationService<TEntity> _entityValidationService;
        private IEntityCacheService<TEntity, TKey> _entityCacheService;

		private readonly ICacheKeyService _cacheKeyService = new CacheKeyFacade();
        private IEnumerable<IBusinessServiceEventsSuccessor<TEntity>> successors;

        #endregion

        #region Property

        /// <summary>
        /// 
        /// </summary>
        protected IEnumerable<IBusinessServiceEventsSuccessor<TEntity>> Successors
        {
            get
            {
                if (successors == null)
                {
                    var list = new List<IBusinessServiceEventsSuccessor<TEntity>>();
                    foreach (var entityInterface in typeof(TEntity).GetInterfaces())
                    {
                        var successorType = typeof (IBusinessServiceEventsSuccessor<>).MakeGenericType(entityInterface);
                        if (DependencyServiceLocator.Current.IsRegistered(successorType))
                        {
                            list.Add(
                                DependencyServiceLocator.Current.GetInstance(successorType) as
                                IBusinessServiceEventsSuccessor<TEntity>);
                        }
                    }
                    successors = list.OrderBy(p => p.Order);
                }

                return successors;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        protected virtual IEntityDataRepository<TEntity, TKey> EntityDataRepository
        {
            get 
			{
            	return _entityDataRepository ?? (_entityDataRepository = DependencyServiceLocator.Current.GetInstance<IEntityDataRepository<TEntity, TKey>>());
            }
        }

        /// <summary>
        /// 
        /// </summary>
        protected virtual IEntityCacheService<TEntity, TKey> EntityCacheService
        {
            get
            {
                if (_entityCacheService == null)
                {
                    var cache = new CacheFacade<TEntity, TKey>();
                    if (cache.CacheIsDefined)
                    {
                        _entityCacheService = cache;
                    }
                }

                return _entityCacheService;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        protected virtual IEntityValidationService<TEntity> EntityValidationService
        {
            get { return _entityValidationService ?? (_entityValidationService = new ValidationFacade<TEntity>()); }
        }

        /// <summary>
        /// 
        /// </summary>
        protected virtual bool CanCaching
        {
            get
            {
                //we cache by default 
                if (_isNeedToCheckForCaching)
                {
                    if (EntityCacheService != null)
                    {
                        if (_typeName != null)
                        {
                            var attribute = Attribute.GetCustomAttribute(Type.GetTypeFromHandle(_typeHandle), typeof(NoCachingAttribute)) as NoCachingAttribute;

                            if (DebugConfiguration.DfTraceSwitch.TraceInfo)
                            {
                                if (attribute != null)
                                {
                                    TraceListenerEx.WriteLine(
                                        string.Format("Entity: Type_{0} has NoCaching attribute", _typeName),
                                        string.Format("{0}. BusinessService - CanCaching", _typeName));
                                }
                            }

                            _canCaching = attribute == null;
                        }
                    }
                    else
                    {
                        if (DebugConfiguration.DfTraceSwitch.TraceInfo)
                        {
                            if (_typeName != null)
                            {
                                TraceListenerEx.WriteLine(
                                        string.Format("Entity: Type_{0} hasn't configurated for caching", _typeName),
                                        string.Format("{0}. BusinessService - CanCaching", _typeName));
                            }
                        } 
                    }

                    _isNeedToCheckForCaching = false;
                }

                return _canCaching;
            }
        }

        #endregion        

        #region Events

        /// <summary>
        /// Used for general pre-processing, this event occurs before validates data and before saving data to the repository.
        /// This is also just before the Creating, Updating and Deleting events are fired.
        /// </summary>
        public event EventHandler<ValidationEntityPreEventArgs<TEntity, TKey>> Validating;

        /// <summary>
        /// Occurs just before saves data to the repository.
        /// </summary>
        public event EventHandler<EntityPreEventArgs<TEntity, TKey>> Creating;

        /// <summary>
        /// Occurs just before saves data to the repository.
        /// </summary>
        public event EventHandler<EntityPreEventArgs<TEntity, TKey>> Updating;

        /// <summary>
        /// Occurs just before saves data to the repository.
        /// </summary>
        public event EventHandler<EntityPreEventArgs<TEntity, TKey>> Deleting;

        /// <summary>
        /// Occurs just before saves data to the repository.
        /// </summary>
        public event EventHandler<EntityEventArgs<TEntity, TKey>> Created;

        /// <summary>
        /// Occurs just before saves data to the repository.
        /// </summary>
        public event EventHandler<EntityEventArgs<TEntity, TKey>> Updated;

        /// <summary>
        /// Occurs just before saves data to the repository.
        /// </summary>
        public event EventHandler<EntityEventArgs<TEntity, TKey>> Deleted;

        #endregion

        #region Raise Events

        /// <summary>
        /// Raises the <see cref="Validating"/> event before validates data.
        /// </summary>
        /// <remarks>
        /// The OnValidating() method also enables derived classes to handle the event without attaching a delegate. 
        /// This is the preferred technique for handling the event in a derived class.
        /// </remarks>
        protected virtual void OnValidating(ValidationEntityPreEventArgs<TEntity, TKey> e)
        {
            EventHandler<ValidationEntityPreEventArgs<TEntity, TKey>> validatingHandler = Validating;

            if (validatingHandler != null)
            {                
                if (DebugConfiguration.DfTraceSwitch.TraceInfo)
                {
                    if (e.Entity != null)
                    {
                        TraceListenerEx.WriteLine(
                            string.Format("Raise validating event for entity: Id_{0};Type_{1}", e.Entity.Id, e.Entity.TypeParameter),
                            string.Format("{0}. BusinessService - OnValidating", _typeName));
                    }
                }

                validatingHandler(this, e);
            }
        }

		private void PreEvent (EntityPreEventArgs<TEntity, TKey> e, string eventName, EventHandler<EntityPreEventArgs<TEntity, TKey>> eventHandler, Func <IBusinessServiceEventsSuccessor<TEntity>, Func<TEntity, SuccessorResult>> sucessorAction)
		{
			ProcessHandler(e, eventName, eventHandler);
			foreach (var successor in Successors)
			{
				var result = sucessorAction(successor)(e.Entity);
				ProcessSuccessorResult(e, result);
			}
		}
		private void PostEvent (EntityEventArgs<TEntity, TKey> e, string eventName, EventHandler<EntityEventArgs<TEntity, TKey>> eventHandler, Func <IBusinessServiceEventsSuccessor<TEntity>, Action<TEntity>> sucessorAction)
		{
			ProcessHandler(e, eventName, eventHandler);
			foreach (var successor in Successors)
			{
				sucessorAction(successor)(e.Entity);
			}
		}

		private void ProcessHandler<T>(T e, string eventName, EventHandler<T> eventHandler) where T : EntityEventArgs<TEntity,TKey>
	    {
			if (eventHandler == null)
			{
				return;
			}
			if (DebugConfiguration.DfTraceSwitch.TraceInfo)
			{
				if (e.Entity != null)
				{
					TraceListenerEx.WriteLine(string.Format("Raise updating event for entity: Id_{0};Type_{1}", e.Entity.Id, e.Entity.TypeParameter), string.Format("{0}. BusinessService - {1}", _typeName, eventName));
				}
			}
			eventHandler(this, e);
	    }

	    /// <summary>
        /// Raises the <see cref="Updating"/> event before saves data to the repository.
        /// </summary>
        /// <remarks>
        /// The OnUpdating() method also enables derived classes to handle the event without attaching a delegate. 
        /// This is the preferred technique for handling the event in a derived class.
        /// </remarks>
        protected virtual void OnUpdating(EntityPreEventArgs<TEntity, TKey> e)
        {
			PreEvent(e, "OnUpdating", Updating, p => p.OnUpdating);
        }

        /// <summary>
        /// Raises the <see cref="Updated"/> event after saves data to the repository.
        /// </summary>
        /// <remarks>
        /// The OnUpdated() method also enables derived classes to handle the event without attaching a delegate. 
        /// This is the preferred technique for handling the event in a derived class.
        /// </remarks>
        protected virtual void OnUpdated(EntityEventArgs<TEntity, TKey> e)
        {
	        PostEvent(e, "OnUpdated", Updated, p => p.OnUpdated);
        }

        /// <summary>
        /// Raises the <see cref="Creating"/> event before saves data to the repository.
        /// </summary>
        /// <remarks>
        /// The OnCreating() method also enables derived classes to handle the event without attaching a delegate. 
        /// This is the preferred technique for handling the event in a derived class.
        /// </remarks>
        protected virtual void OnCreating(EntityPreEventArgs<TEntity, TKey> e)
        {
			PreEvent(e, "OnCreating", Creating, p => p.OnCreating);
        }

        /// <summary>
        /// Raises the <see cref="Created"/> event after saves data to the repository.
        /// </summary>
        /// <remarks>
        /// The OnCreated() method also enables derived classes to handle the event without attaching a delegate. 
        /// This is the preferred technique for handling the event in a derived class.
        /// </remarks>
        protected virtual void OnCreated(EntityEventArgs<TEntity, TKey> e)
        {
			PostEvent(e, "OnCreated", Created, p => p.OnCreated);
        }

        /// <summary>
        /// Raises the <see cref="Deleting"/> event before saves data to the repository.
        /// </summary>
        /// <remarks>
        /// The OnDeleting() method also enables derived classes to handle the event without attaching a delegate. 
        /// This is the preferred technique for handling the event in a derived class.
        /// </remarks>
        protected virtual void OnDeleting(EntityPreEventArgs<TEntity, TKey> e)
        {
			PreEvent(e, "OnDeleting", Deleting, p => p.OnDeleting);
        }

		/// <summary>
		/// Raises the <see cref="Deleted"/> event after saves data to the repository.
		/// </summary>
		/// <remarks>
		/// The OnDeleted() method also enables derived classes to handle the event without attaching a delegate. 
		/// This is the preferred technique for handling the event in a derived class.
		/// </remarks>
		protected virtual void OnDeleted(EntityEventArgs<TEntity, TKey> e)
		{
			PostEvent(e, "OnDeleted", Deleted, p => p.OnDeleted);
		}

        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        /// <param name="result"></param>
        protected virtual void ProcessSuccessorResult(EntityPreEventArgs<TEntity, TKey> e, SuccessorResult result)
        {
            switch (result)
            {
                case SuccessorResult.Add:
                    InternalAdd(e.Entity);
                    e.Cancel = true;
                    break;
                case SuccessorResult.Save:
                    InternalSave(e.Entity);
                    e.Cancel = true;
                    break;
                case SuccessorResult.Delete:
                    InternalRemove(e.Entity);
                    e.Cancel = true;
                    break;
                case SuccessorResult.Cancel:
                    e.Cancel = true;
                    break;
            }
        }

        #endregion

        #region IEntityBusinessService<T> Members

        /// <summary>
        /// 
        /// </summary>
        public virtual TEntity Entity
        {
            get
            {
                return new TEntity();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entityObject"></param>
        /// <returns></returns>
        protected virtual bool CanAdd(TEntity entityObject)
        {
            Guard.ArgumentNotNull(entityObject, "entityObject");
            
            return entityObject.IsNew;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entityObject"></param>
        /// <returns></returns>
        protected virtual bool CanSave(TEntity entityObject)
        {
            Guard.ArgumentNotNull(entityObject, "entityObject");

            return entityObject.IsCreated;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entityObject"></param>
        /// <returns></returns>
        protected virtual bool CanRemove(TEntity entityObject)
        {
            Guard.ArgumentNotNull(entityObject, "entityObject");
            
            return entityObject.IsCreated;
        }

		private void EditEntity(TEntity entityObject, string actionName, Func<TEntity, bool> canPerformAction, Action<TEntity> internalAction, Action<EntityPreEventArgs<TEntity, TKey>> onActionStarted, Action<EntityEventArgs<TEntity, TKey>> onActionCompleted)
		{
			Guard.ArgumentNotNull(entityObject, "entityObject");

			var eventArgs = new EntityPreEventArgs<TEntity, TKey>
			{
				Entity = entityObject
			};

			onActionStarted(eventArgs);

			string category = string.Format("{0}. BusinessService - {1}", _typeName, actionName);

			if (!eventArgs.Cancel)
			{
				Validate(entityObject, ValidationActionType.Create);

				if (!canPerformAction(entityObject))
				{
					Trace(string.Format("Can't {2} entity: Id_{0};Type_{1}", entityObject.Id, entityObject.TypeParameter, actionName), category);
					throw new BusinessProcessException(entityObject, actionName);
				}

				Trace(string.Format("Entity: Id_{0};Type_{1} is passed to db level", entityObject.Id, entityObject.TypeParameter), category);

				internalAction(entityObject);

				// fire post-create event
				onActionCompleted(eventArgs);
			}
			else
			{
				Trace(string.Format("{2} canceled by external event for entity: Id_{0};Type_{1}", entityObject.Id, entityObject.TypeParameter, actionName), category);
			}
		}

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entityObject"></param>
        public virtual void Add(TEntity entityObject)
        {
	        EditEntity(entityObject, "Add", CanAdd, InternalAdd, OnCreating, OnCreated);
        }


		/// <summary>
		/// 
		/// </summary>
		/// <param name="entityObject"></param>
		public virtual void Save(TEntity entityObject)
		{
			EditEntity(entityObject, "Save", CanSave, InternalSave, OnUpdating, OnUpdated);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="entityObject"></param>
		public virtual void Remove(TEntity entityObject)
		{
			EditEntity(entityObject, "Remove", CanRemove, InternalRemove, OnDeleting, OnDeleted);
		}

		private void InternalEdit (TEntity entityObject, string actionName, Action<TEntity> internalAction, Action<string, TEntity> cacheAction)
		{
			string category = string.Format("{0}. BusinessService - {1}", _typeName, actionName);

			using (new UnitOfWorkScope<TEntity>())
			{
				internalAction(entityObject);
			}

			if (CanCaching)
			{
				if (EntityDataRepository.CacheOptions != DataRepositoryCacheOptions.NoCache)
				{
					string cacheKey = _cacheKeyService.GenerateCacheKey(entityObject);

					Trace(string.Format("Entity: Id_{0};Type_{1} is passed to cache with key {2}", entityObject.Id, entityObject.TypeParameter, cacheKey), category);
					Trace(string.Format("Entity: Id_{0};Type_{1} raises clear cache dependency for type {1}", entityObject.Id,entityObject.TypeParameter), category);

					cacheAction(cacheKey, entityObject);
					EntityCacheService.ClearDependency(entityObject);
				}
				else
				{
					Trace(string.Format("Entity: Id_{0};Type_{1} isn't passed to cache. Reason - cache option equals to NoCache", entityObject.Id, entityObject.TypeParameter), category);
				}
			}
		}

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entityObject"></param>
        protected virtual void InternalAdd(TEntity entityObject)
        {
	        InternalEdit(entityObject, "Add",  EntityDataRepository.Add, (p, q) => EntityCacheService.AddItemToCache(p, q));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entityObject"></param>
        protected virtual void InternalSave(TEntity entityObject)
        {
			InternalEdit(entityObject, "Save", EntityDataRepository.Save, (p, q) => EntityCacheService.AddItemToCache(p, q));
        }
		
        /// <summary>
        /// 
        /// </summary>
        /// <param name="entityObject"></param>
        protected virtual void InternalRemove(TEntity entityObject)
        {
			InternalEdit(entityObject, "Remove", EntityDataRepository.Remove, (p, q) => EntityCacheService.RemoveItemFromCache(p));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        public virtual void Remove(TKey id)
        {
            string category = string.Format("{0}. BusinessService - Remove", _typeName);

            var eventArgs = new EntityPreEventArgs<TEntity, TKey>
            {
                Id = id
            };

            // fire pre-create event
            OnDeleting(eventArgs);

            if (!eventArgs.Cancel)
            {
                Trace(string.Format("Entity: Id_{0};Type_{1} is passed to db level", id, _typeName), category);
                
                using (new UnitOfWorkScope<TEntity>())
                {
                	EntityDataRepository.Remove(id);
                }

                if (CanCaching)
                {
                    if (EntityDataRepository.CacheOptions != DataRepositoryCacheOptions.NoCache)
                    {
						string cacheKey = _cacheKeyService.GenerateCacheKey(id);
                        Trace(string.Format("Entity: Id_{0};Type_{1} is passed to cache with key {2}", id, _typeName, cacheKey), category);
                        Trace(string.Format("Entity: Id_{0};Type_{1} raises clear cache dependency for type {1}", id, _typeName), category);
                        
                        EntityCacheService.RemoveItemFromCache(cacheKey);

                        EntityCacheService.ClearDependency(id);
                    }
                    else
                    {
                        Trace(string.Format("Entity: Id_{0};Type_{1} isn't passed to cache. Reason - cache option equals to NoCache", id, _typeName), category);                        
                    }
                }

                // fire post-create event
                OnDeleted(eventArgs);
            }
            else
            {
                Trace(string.Format("Remove canceled by external event for entity: Id_{0};Type_{1}", id, _typeName), category);                
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public virtual TEntity FindOne(TKey id)
        {
            var slicer = SlicerServiceFacade.Scope.GetCurrentSlicer(typeof (TEntity));
            var category = string.Format("{0}. BusinessService - FindOne", _typeName);
			var cacheResult = GetOneFromCache(p => p.GenerateCacheKey(typeof(TEntity), id, slicer), "FindOne(TKey)");
        	var result = cacheResult.Result;

            if (result == null)
            {
                Trace("FindOne(id) method looks up to db", category);
                
                using (new UnitOfWorkScope<TEntity>())
                {
                	result = EntityDataRepository.FindOne(id);
                }

                if ((result != null) && (result.IsCreated))
                {
                    if (CanCaching)
                    {
                        if (EntityDataRepository.CacheOptions != DataRepositoryCacheOptions.NoCache)
                        {
                            Trace(string.Format("FindOne(id) method passes data with key:{0} to the cache", cacheResult.CacheKey), category);
                            
							EntityCacheService.AddItemToCache(cacheResult.CacheKey, result);

							EntityCacheService.AddToDependency(cacheResult.CacheKey);
                        }
                        else
                        {
                            Trace("FindOne(id) method doesn't look up to cache. Reason - cache option equals to NoCache", category);                            
                        }
                    }
                }
                else
                {
                    Trace("FindOne(id) method finds nothing", category);                    
                }
            }

            return result;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        public virtual TEntity FindOne(Expression<Func<TEntity, bool>> expression)
        {
            Guard.ArgumentNotNull(expression, "expression");
            var slicer = SlicerServiceFacade.Scope.GetCurrentSlicer(typeof(TEntity));
            var category = string.Format("{0}. BusinessService - FindOne", _typeName);
			var cacheResult = GetOneFromCache(p => p.GenerateCacheKey(typeof(TEntity), expression, slicer), "FindOne(Expression)");
			var result = cacheResult.Result;

            if (result == null)
            {
                Trace("FindOne(expression) method looks up to db", category);
                
                using (new UnitOfWorkScope<TEntity>())
                {
                	result = EntityDataRepository.FindOne(expression);
                }

                if ((result != null) && (result.IsCreated))
                {
                    if (CanCaching)
                    {
                        if (EntityDataRepository.CacheOptions == DataRepositoryCacheOptions.FullCache)
                        {
                            Trace(string.Format("FindOne(expression) method passes data with key:{0} to the cache", cacheResult.CacheKey), category);
                            Trace(string.Format("FindOne(expression) method raises add to cache dependency for type {0}", _typeName), category);
                            
							EntityCacheService.AddItemToCache(cacheResult.CacheKey, result);

							EntityCacheService.AddToDependency(cacheResult.CacheKey);
                        }
                        else
                        {
                            Trace("FindOne(expression) method doesn't look up to cache. Reason - cache option not equals to FullCache", category);                            
                        }
                    }
                }
                else
                {
                    Trace("FindOne(expression) method finds nothing", category);                    
                }
            }

            return result;
        }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="expression"></param>
		/// <param name="selectExpression"></param>
		/// <returns></returns>
    	public virtual TEntity FindOne(Expression<Func<TEntity, bool>> expression, Expression<Func<TEntity, object>> selectExpression)
    	{
			Guard.ArgumentNotNull(expression, "expression");

            var slicer = SlicerServiceFacade.Scope.GetCurrentSlicer(typeof(TEntity));
			var category = string.Format("{0}. BusinessService - FindOne", _typeName);
			var cacheResult = GetOneFromCache(p => p.GenerateCacheKey(typeof(TEntity), expression, selectExpression, slicer), "FindOne(Expression, SelectExpression)");
			var result = cacheResult.Result;

			if (result == null)
			{
				Trace("FindOne(expression) method looks up to db", category);

				using (new UnitOfWorkScope<TEntity>())
				{
					result = EntityDataRepository.FindOne(expression, selectExpression);
				}

				if ((result != null) && (result.IsCreated))
				{
					if (CanCaching)
					{
						if (EntityDataRepository.CacheOptions == DataRepositoryCacheOptions.FullCache)
						{
							Trace(string.Format("FindOne(expression) method passes data with key:{0} to the cache", cacheResult.CacheKey), category);
							Trace(string.Format("FindOne(expression) method raises add to cache dependency for type {0}", _typeName), category);

							EntityCacheService.AddItemToCache(cacheResult.CacheKey, result);

							EntityCacheService.AddToDependency(cacheResult.CacheKey);
						}
						else
						{
							Trace("FindOne(expression) method doesn't look up to cache. Reason - cache option not equals to FullCache", category);
						}
					}
				}
				else
				{
					Trace("FindOne(expression) method finds nothing", category);
				}
			}

			return result;
		}

    	/// <summary>
        /// 
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        public virtual IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> expression)
        {
            Guard.ArgumentNotNull(expression, "expression");
            var slicer = SlicerServiceFacade.Scope.GetCurrentSlicer(typeof(TEntity));
    		var result = GetListFromCache(p => p.GenerateCacheKey(typeof(TEntity), expression, slicer), "Find(Expression)");
			return result.Result ?? RegisterResultCallback(() => EntityDataRepository.Find(expression), result.CacheKey, false);
        }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="expression"></param>
		/// <param name="selectExpression"></param>
		/// <returns></returns>
    	public virtual IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> expression, Expression<Func<TEntity, object>> selectExpression)
    	{
			Guard.ArgumentNotNull(expression, "expression");
			Guard.ArgumentNotNull(expression, "selectExpression");
            var slicer = SlicerServiceFacade.Scope.GetCurrentSlicer(typeof(TEntity));
			var result = GetListFromCache(p => p.GenerateCacheKey(typeof(TEntity), expression, selectExpression, slicer), "Find(Expression, SelectExpression)");
			return result.Result ?? RegisterResultCallback( () => EntityDataRepository.Find(expression, selectExpression), result.CacheKey, false);
    	}

    	/// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public virtual IEnumerable<TEntity> FindAll()
        {
            var slicer = SlicerServiceFacade.Scope.GetCurrentSlicer(typeof(TEntity));
            var result = GetListFromCache(p => p.GenerateCacheKey(typeof(TEntity), slicer), "FindAll()");
            return result.Result ?? RegisterResultCallback(() => EntityDataRepository.FindAll(), result.CacheKey, false);
        }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="selectExpression"></param>
		/// <returns></returns>
    	public virtual IEnumerable<TEntity> FindAll(Expression<Func<TEntity, object>> selectExpression)
    	{
			Guard.ArgumentNotNull(selectExpression, "selectExpression");

            var slicer = SlicerServiceFacade.Scope.GetCurrentSlicer(typeof(TEntity));
			var result = GetListFromCache(p => p.GenerateCacheKey(typeof(TEntity), selectExpression, slicer), "FindAll(SelectExpression)");
			return result.Result ?? RegisterResultCallback(() => EntityDataRepository.FindAll(selectExpression), result.CacheKey, false);
    	}

    	/// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <returns></returns>
        public virtual TEntity FindOne(IEntityCommand<TEntity> command)
        {
            return FindOne(command, false);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="clearDependency"></param>
        /// <returns></returns>
        public virtual TEntity FindOne(IEntityCommand<TEntity> command, bool clearDependency)
        {
            Guard.ArgumentNotNull(command, "command");
			var category = string.Format("{0}. BusinessService - FindOne", _typeName);
			var cacheResult = GetOneFromCache(p => p.GenerateCacheKey(typeof(TEntity), command), "FindOne()");
			var result = cacheResult.Result;

            if (result == null)
            {
                Trace("FindOne method looks up to db", category);
                
                using (new UnitOfWorkScope<TEntity>())
                {
                	result = EntityDataRepository.FindOne(command);
                }

                if ((result != null) && (result.IsCreated))
                {
                    if (CanCaching)
                    {
                        if (clearDependency)
                        {
                            Trace(string.Format("FindOne method raises clear cache dependency for type {0}", _typeName), category);
                            
                            EntityCacheService.ClearCache();
                        }
                        else
                        {
                            if (EntityDataRepository.CacheOptions == DataRepositoryCacheOptions.FullCache)
                            {
								if (!string.IsNullOrEmpty(cacheResult.CacheKey))
                                {
                                    Trace(string.Format("FindOne method passes data with key:{0} to the cache", cacheResult.CacheKey), category);
                                    Trace(string.Format("FindOne method raises add to cache dependency for type {0}", _typeName), category);
                                    
									EntityCacheService.AddItemToCache(cacheResult.CacheKey, result);

									EntityCacheService.AddToDependency(cacheResult.CacheKey);
                                }
                                else
                                {
                                    Trace("FindOne method doesn't add data to cache. Reason - cache key is empty", category);                                    
                                }
                            }
                            else
                            {
                                Trace("FindOne method doesn't look up to cache. Reason - cache option not equals to FullCache", category);
                            }
                        }
                    }
                }
                else
                {
                    Trace("FindOne method finds nothing", category);
                }
            }

            return result;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <returns></returns>
        public virtual IEnumerable<TEntity> Find(IEntityCommand<TEntity> command)
        {
            return Find(command, false);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="clearDependency"></param>
        /// <returns></returns>
        public virtual IEnumerable<TEntity> Find(IEntityCommand<TEntity> command, bool clearDependency)
        {
            Guard.ArgumentNotNull(command, "command");
			var result = GetListFromCache(p => p.GenerateCacheKey(typeof(TEntity), command), "Find(Command)");
			return result.Result ?? RegisterResultCallback(() => EntityDataRepository.Find(command), result.CacheKey, clearDependency);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        public virtual void Execute(IEntityCommand<TEntity> command)
        {
            Guard.ArgumentNotNull(command, "command");

            string category = string.Format("{0}. BusinessService - Execute", _typeName);

            Trace("Execute method calls db methods", category);
            
            using (new UnitOfWorkScope<TEntity>())
            {
            	EntityDataRepository.Execute(command);
            }

            if (CanCaching)
            {
                if (EntityDataRepository.CacheOptions == DataRepositoryCacheOptions.FullCache)
                {
                    Trace(string.Format("Execute method raises clear cache dependency for type {0}", _typeName), category);
                    
                    EntityCacheService.ClearCache();
                }
                else
                {
                    Trace("Execute method doesn't look up to cache. Reason - cache option not equals to FullCache", category);
                }
            }
        }


        #region Obsolete members
        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <returns></returns>
        [Obsolete("Use FindOne method instead")]
        public TEntity FindOneByCommand(IEntityCommand<TEntity> command)
        {
            return FindOne(command);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="clearDependency"></param>
        /// <returns></returns>
        [Obsolete("Use FindOne method instead")]
        public TEntity FindOneByCommand(IEntityCommand<TEntity> command, bool clearDependency)
        {
            return FindOne(command, clearDependency);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <returns></returns>
        [Obsolete("Use Find method instead")]
        public IEnumerable<TEntity> FindByCommand(IEntityCommand<TEntity> command)
        {
            return Find(command);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="clearDependency"></param>
        /// <returns></returns>
        [Obsolete("Use Find method instead")]
        public IEnumerable<TEntity> FindByCommand(IEntityCommand<TEntity> command, bool clearDependency)
        {
            return Find(command, clearDependency);
        }
        #endregion

        #endregion

        #region Methods

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entityObject"></param>
        /// <param name="actionType"></param>
        protected void Validate(TEntity entityObject, ValidationActionType actionType)
        {
            Guard.ArgumentNotNull(entityObject, "entityObject");

            string category = string.Format("{0}. BusinessService - Validation", _typeName);

            Trace(string.Format("Validation of entity: Id_{0};Type_{1}", entityObject.Id, entityObject.TypeParameter), category);
            
            var eventArgs = new ValidationEntityPreEventArgs<TEntity, TKey>
            {
                Entity = entityObject,
                ValidationAction = actionType
            };

            OnValidating(eventArgs);

            if (!eventArgs.Cancel)
            {
                ValidationResult validationResult = EntityValidationService.Validate(entityObject, actionType);

                if (!validationResult.IsValid)
                {
                    Trace(string.Format("Not valid entity: Id_{0};Type_{1}. Message: {2}", entityObject.Id, entityObject.TypeParameter, validationResult.ValidationMessage), category);
                    
                    throw new ValidationException(validationResult.ValidationMessage);
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="selector"></param>
        /// <param name="cacheKey"></param>
        /// <param name="clearDependency"></param>
        /// <returns></returns>
        protected IEnumerable<TEntity> RegisterResultCallback(Func<IQueryable<TEntity>> selector, string cacheKey, bool clearDependency)
        {
            Guard.ArgumentNotNull(selector, "selector");

            string methodName = selector.Method.Name;
            string categoryName = string.Format("{0}. BusinessService - {1}", _typeName, methodName);

            return new SimplePipeline<TEntity>()
                    .Register(p =>
                            {
								Trace(string.Format("{0} method looks up to db", methodName), categoryName);
								
								using (new UnitOfWorkScope<TEntity>())
                              	{
                              			return selector().ToList();
                              	}
                            })
                    .Register(p =>
                    {
                        if (CanCaching)
                        {
                            if (clearDependency)
                            {
                                Trace(string.Format("{0} method raises clear cache dependency for type {1}", methodName, _typeName), categoryName);
                                
                                EntityCacheService.ClearCache();
                            }
                            else
                            {
                                if (EntityDataRepository.CacheOptions == DataRepositoryCacheOptions.FullCache)
                                {
                                    if (!string.IsNullOrEmpty(cacheKey))
                                    {
                                        Trace(string.Format("{0} method passes data with key:{1} to the cache", methodName, cacheKey), categoryName);
                                        Trace(string.Format("{0} method raises add to cache dependency for type {1}", methodName, _typeName), categoryName);                                        

                                        EntityCacheService.AddListToCache(cacheKey, p);

                                        EntityCacheService.AddToDependency(cacheKey);
                                    }
                                    else
                                    {
                                        Trace(string.Format("{0} method doesn't add data to cache for type {1}. Reason - cache key is empty", methodName, _typeName), categoryName);                                        
                                    }
                                }
                                else
                                {
                                    Trace(string.Format("{0} method doesn't look up to cache. Reason - cache option not equals to FullCache", methodName), categoryName);                                    
                                }
                            }
                        }

                        return p;
                    })
                    .ReturnChain();
        }

        #endregion

		private static void Trace(string message, string category)
		{
			if (DebugConfiguration.DfTraceSwitch.TraceInfo)
			{
				TraceListenerEx.WriteLine(message, category);
			}
		}
		/// <summary>
		/// 
		/// </summary>
		/// <param name="cacheKeyBuilder"></param>
		/// <param name="method"></param>
		/// <returns></returns>
		protected CacheResult<TEntity> GetOneFromCache(Func<ICacheKeyService, string> cacheKeyBuilder, string method)
		{
			return GetFromCache(cacheKeyBuilder, method, EntityDataRepository.CacheOptions != DataRepositoryCacheOptions.NoCache, (service, key) => service.GetFromCache(key));
		}
		/// <summary>
		/// 
		/// </summary>
		/// <param name="cacheKeyBuilder"></param>
		/// <param name="method"></param>
		/// <returns></returns>
		protected CacheResult<IList<TEntity>> GetListFromCache(Func<ICacheKeyService, string> cacheKeyBuilder, string method)
		{
			return GetFromCache(cacheKeyBuilder, method, EntityDataRepository.CacheOptions != DataRepositoryCacheOptions.NoCache, (service, key) => service.GetListFromCache(key));
		}

		private CacheResult<T> GetFromCache<T>(Func<ICacheKeyService, string> cacheKeyBuilder, string method, bool isEntityCacheEnabled, Func<IEntityCacheService<TEntity, TKey>, string, T> cacheAccessor)
		{
			var category = string.Format("{0}. BusinessService - {1}", _typeName, method);
			var result = new CacheResult<T>();
			if (CanCaching)
			{
				if (isEntityCacheEnabled)
				{
					var cacheKey = cacheKeyBuilder(_cacheKeyService);
					Trace(string.Format("Looks up to cache by key:{0}", cacheKey), category);
					result.Result = cacheAccessor(EntityCacheService, cacheKey);
					result.CacheKey = cacheKey;
				}
				else
				{
					Trace(string.Format("Doesn't look up to cache. Reason - cache is disabled for this entity"), category);
				}
			}

			return result;
		}
    }
}
